#include <stdio.h>
#include <math.h>

#include "raylib.h"

#include "convert.h"
#include "dynamic_array.h"
#define DYN_STR_IMPLEMENTATION
#include "dynamic_string.h"
#include "types.h"

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define TARGET_FPS 144
#define MARGIN 10
#define PREVIEW_LINES 10
#define FONT_SIZE 20
#define POINTER_RADIUS MARGIN*0.5
#define POINTER_COLOR RED

f32 lerp(f32 x, f32 in_min, f32 in_max, f32 out_min, f32 out_max) {
    return (x - in_min)/(in_max - in_min)*(out_max - out_min) + out_min;
}

void draw_axes(Vector2 max_vals, Vector2 min_vals, Rectangle inner_axis_rect) {
    // vertical axis
    Vector2 start = {
        .x = inner_axis_rect.x,
        .y = inner_axis_rect.y,
    };
    Vector2 stop = {
        .x = start.x,
        .y = start.y + inner_axis_rect.height,
    };
    char label[256];
    f32 axis_thickness = 2;
    f32 arrow_length = 10;
    f32 angle = 40;
    i32 axis_font_size = 20;
    Vector2 arrow_start = {0};
    Vector2 base_arrow = {0};

    base_arrow.x = sinf(angle*DEG2RAD)*arrow_length;
    base_arrow.y = cosf(angle*DEG2RAD)*arrow_length;
    Vector2 text_start = {0};

    // draw vertical axis
    if(max_vals.x < 0) {
        start.x += inner_axis_rect.width;
        stop.x = start.x;
    } else if (min_vals.x > 0) {
        // do nothing; values are already set!
    } else {
        f32 x_remap = lerp(
            0, // seeking the origin
            min_vals.x, // input data min
            max_vals.x, // input data max
            inner_axis_rect.x, // drawing coords min
            inner_axis_rect.x + inner_axis_rect.width // dc max
        );
        start.x = x_remap;
        stop.x = x_remap;
    }
    DrawLineEx(start, stop, axis_thickness, BLACK);
    arrow_start.x = stop.x - base_arrow.x;
    arrow_start.y = start.y + base_arrow.y;
    DrawLineEx(arrow_start, start, axis_thickness, BLACK);
    arrow_start.x = base_arrow.x + stop.x;
    DrawLineEx(arrow_start, start, axis_thickness, BLACK);
    
    // y-min
    sprintf(label, "%.01f", roundf(max_vals.y));
    Vector2 label_size = MeasureTextEx(
        GetFontDefault(),
        label,
        axis_font_size,
        axis_font_size/10
    );
    DrawText(label, start.x - (15+label_size.x), start.y + label_size.y/2, axis_font_size, BLACK);
    
    // y-max
    sprintf(label, "%.01f", roundf(min_vals.y));
    label_size = MeasureTextEx(
        GetFontDefault(),
        label,
        axis_font_size,
        axis_font_size/10
    );
    DrawText(label, stop.x - (15+label_size.x), stop.y - label_size.y, axis_font_size, BLACK);

    // indicate that the axis crossing is not at the origin
    if (min_vals.y > 0) {
        sprintf(label, "==");
        label_size = MeasureTextEx(
            GetFontDefault(),
            label,
            axis_font_size/2,
            axis_font_size/20
        );
        DrawText(label, start.x - label_size.x, stop.y - 20, 20, BLACK);
    }

    // draw horizontal axis
    start.x = inner_axis_rect.x;
    stop.x = start.x + inner_axis_rect.width;
    start.y = inner_axis_rect.y + inner_axis_rect.height;
    stop.y = start.y;
    if(max_vals.y < 0) {
        start.y = inner_axis_rect.y;
        stop.y = start.y;
    } else if (min_vals.y > 0) {
        // do nothing; values are already set!
    } else {
        f32 y_remap = lerp(
            0, // seeking the origin
            min_vals.y, // input data min
            max_vals.y, // input data max
            inner_axis_rect.y + inner_axis_rect.height, // drawing coords min
            inner_axis_rect.y // dc max
        );
        start.y = y_remap;
        stop.y = y_remap;
    }
    
    DrawLineEx(start, stop, axis_thickness, BLACK);
    arrow_start.x = stop.x - base_arrow.y;
    arrow_start.y = start.y + base_arrow.x;
    DrawLineEx(arrow_start, stop, axis_thickness, BLACK);
    arrow_start.y = start.y - base_arrow.x;
    DrawLineEx(arrow_start, stop, axis_thickness, BLACK);
    
    // x-min
    sprintf(label, "%.01f", roundf(min_vals.x));
    label_size = MeasureTextEx(
        GetFontDefault(),
        label,
        axis_font_size,
        axis_font_size/10
    );
    DrawText(label, start.x, start.y + 10, 20, BLACK);
    
    // x-max
    sprintf(label, "%.01f", roundf(max_vals.x));
    label_size = MeasureTextEx(
        GetFontDefault(),
        label,
        axis_font_size,
        axis_font_size/10
    );
    DrawText(label, stop.x - label_size.x, stop.y + 10, 20, BLACK);

    // indicate that the axis crossing is not at the origin
    if (min_vals.x > 0) {
        sprintf(label, "||");
        label_size = MeasureTextEx(
            GetFontDefault(),
            label,
            axis_font_size/2,
            axis_font_size/20
        );
        DrawText(label, start.x + 10, stop.y - label_size.y, 20, BLACK);
    }
}

void scatter_plot(F32Column *x, F32Column *y, Vector2 *boundary) {
    u64 n = 0;
    f32 margin = 20;
    // set "n" to the smallest of the lengths
    n = x->length < y->length ? x->length : y->length;

    Rectangle outer_axis_rect = {
        .x = margin,
        .y = margin,
        .width = boundary->x - 2*margin,
        .height = boundary->y - 2*margin,
    };

    Rectangle inner_axis_rect = {
        .x = outer_axis_rect.x + margin,
        .y = outer_axis_rect.y + margin,
        .width = outer_axis_rect.width - 2*margin,
        .height = outer_axis_rect.height - 2*margin,
    };

    DrawRectangleRec(outer_axis_rect, LIGHTGRAY);

    // prepare and draw axes
    Vector2 min_vals = { 1e30, 1e30 };
    Vector2 max_vals = { -1e30, -1e30 };
    for(u64 i=0; i<n; ++i) {
        f32 xx = x->items[i];
        f32 yy = y->items[i];
        min_vals.x = xx < min_vals.x ? xx : min_vals.x;
        max_vals.x = xx > max_vals.x ? xx : max_vals.x;
        min_vals.y = yy < min_vals.y ? yy : min_vals.y;
        max_vals.y = yy > max_vals.y ? yy : max_vals.y;
    }
    draw_axes(max_vals, min_vals, inner_axis_rect);

    f32 x_span = fabs(max_vals.x - min_vals.x);
    x_span = x_span < 1e-6 ? 1.0f : x_span;
    x_span = 1.0f / x_span;
    f32 y_span = fabs(max_vals.y - min_vals.y);
    y_span = y_span < 1e-6 ? 1.0f : y_span;
    y_span = 1.0f / y_span;
    
    for(u64 i=0; i<n; ++i) {
        Vector2 center = {0};
        f32 xx = (x->items[i] - min_vals.x)*x_span;
        f32 yy = (y->items[i] - min_vals.y)*y_span;
        center.x = xx*inner_axis_rect.width + inner_axis_rect.x;
        center.y = (1.0f - yy)*inner_axis_rect.height + inner_axis_rect.y;
        DrawCircleV(center, 2, DARKGRAY);
    }

}

int main(void) {
    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_ALWAYS_RUN);
    InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Data Plotter");
    SetTargetFPS(TARGET_FPS);

    F32Column x = {0};
    F32Column y = {0};
    Vector2 screen_size = {0};
    for(u64 i=0; i<1000; ++i) {
        f32 xx = GetRandomValue(1024, 2048)/1024.0f;
        f32 yy = GetRandomValue(-1024, 2048)/1024.0f;
        // f32 r = GetRandomValue(0, 1024)/1024.0f;
        // f32 rescale = r/sqrtf(xx*xx + yy*yy);
        // xx *= rescale;
        // yy *= rescale;
        dyn_array_append(&x, xx);
        dyn_array_append(&y, yy);
    }

    while (!WindowShouldClose()) {
        screen_size.x = GetScreenWidth();
        screen_size.y = GetScreenHeight();
        BeginDrawing();
        ClearBackground(DARKGRAY);
        scatter_plot(&x, &y, &screen_size);
        char npts_str[256];
        sprintf(npts_str, "#pts: %d", (i32)x.length);
        DrawText(npts_str, 5, 5, 22, BLUE);
        sprintf(npts_str, "FPS: %.3g", 1/GetFrameTime());
        DrawText(npts_str, screen_size.x - 120, 5, 22, BLUE);

        EndDrawing();
    }
    CloseWindow();
    return 0;
}